﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace LambdaManAi
{
    public delegate object UserFunction(object[] args);

    public class UserFunctions : LowLevelFunctions
    {
        //Args for the step
        private const int AiState = 0;
        private const int WorldState = 1;

        //4-tuple world
        private const int MapStatus = 0;
        private const int ManStatus = 1;
        private const int GhostSatatus = 2;
        private const int FruitSatus = 3;
        
        //Map cells
        private const int Wall = 0;
        private const int EmptySpace = 1;
        private const int Pill = 2;
        private const int PowerPill = 3;
        private const int Fruit = 4;
        private const int ManStartPosition = 5;
        private const int GhostStartPosition = 6;
        
        //Directions
        private const int MoveUp = 0;
        private const int MoveRight = 1;
        private const int MoveDown = 2;
        private const int MoveLeft = 3;

        //Man 5-tuple, ghost 3-tuple
        private const int Vitality = 0;
        private const int Location = 1;
        private const int Direction = 2;
        private const int Lives = 3;
        private const int Score = 4;

        //Ghost Vitality
        private const int Standard = 0;
        private const int Fright = 1;
        private const int Invisible = 2;

        //Cells price
        private const int PillPriсe = 10;        
        private const int PowerPillPriсe = 500;
        private const int FruitPriсe = 1000;


        #region Auxiliary code, don't modify it!

        public static IProcessor Processor { get; set; }

        private static object Code(Expression<Func<object>> expression, [CallerMemberName]string functionName = "")
        {
            return Processor.ProcessCode(functionName, expression);
        }

        private static object Code(Locals locals, Expression<Func<object>> expression, [CallerMemberName]string functionName = "")
        {
            return Processor.ProcessCode(functionName, locals, expression);
        }

        #endregion

        public static object MainFunction(params object[] args)
        {
            return Code(() =>
                Pair(Pair(1, List()), Closure(StepFunction))
            );
        }

        public static object StepFunction(params object[] args)
        {
            // arguments
            const int stepState = 0;
            const int worldState = 1;

            Locals locals = new Locals(args);

            return Code(locals, () => 
                (int)GetItem(locals[stepState], 0) == 2
                    ? Pair(
                        Pair(IsEmptyList(GetTail(GetLastTupleItem(locals[stepState], 1))) ? 1 : 2, GetTail(GetLastTupleItem(locals[stepState], 1))),
                        GetItem(GetLastTupleItem(locals[stepState], 1), 0))
                    : StepFunction2(locals[stepState], locals[worldState])
                );
        }

        public static object StepFunction2(params object[] args)
        {
            // arguments
            const int stepState = 0;
            const int worldState = 1;

            // locals (should have numbers just after arguments!)
            const int map = 2;
            const int man = 3;
            const int manY = 4;
            const int manX = 5;
            const int maxNeighbor = 6;
            const int path = 7;
            const int curcell = 8;
            const int upcell = 9;
            const int rightcell = 10;
            const int downcell = 11;
            const int leftcell = 12;
            const int nextPath = 13;

            Locals locals = null;
            locals = new Locals(args)
            {
                {map, () => GetItem(locals[worldState], MapStatus)},
                {man, () => GetItem(locals[worldState], ManStatus)},
                {manY, () => GetItem(GetItem(locals[man], 1), 1)},
                {manX, () => GetItem(GetItem(locals[man], 1), 0)},
                {maxNeighbor, () => GetMaxNeighbor(locals[map], locals[manY], locals[manX])},
                {path, () => NextCell(locals[map], locals[manY], locals[manX], 0, -1)},
                {curcell, () =>NormalizeCell(GetCell(locals[map], locals[manY], locals[manX]))},
                {upcell, () => (int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], (int)locals[manY]-1, locals[manX]))},
                {rightcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX]+1))},
                {downcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], (int)locals[manY]+1, locals[manX]))},
                {leftcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX]-1))},
                {nextPath, () => IsEmptyList(locals[path]) ? List() : GetTail(locals[path]) },
            };

            //return Code(locals, () =>
            //    Pair(
            //        List(1, Pair(2, 3), 4, 5, Tuple(6, 7, 8), List()),
            //        Select(
            //            locals[maxNeighbor] == NormalizeCell(GetCell(locals[map], (int)locals[manY] - 1, locals[manX])),
            //            MoveUp,
            //            Select(
            //                locals[maxNeighbor] == NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX] + 1)),
            //                MoveRight,
            //                Select(
            //                    locals[maxNeighbor] == NormalizeCell(GetCell(locals[map], (int)locals[manY] + 1, locals[manX])),
            //                    MoveDown,
            //                    MoveLeft
            //                    )
            //                )
            //            )
            //        )
            //);

            //return Code(locals, () =>
            //    Pair(1, Max4Index(
            //        NormalizeCell(GetCell(locals[map], (int)locals[manY] - 1, locals[manX])),
            //        NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX] + 1)),
            //        NormalizeCell(GetCell(locals[map], (int)locals[manY] + 1, locals[manX])),
            //        NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX] - 1)))
            //    )
            //);

            //return Code(locals, () =>
            //    Pair(1, Max4Index(
            //        CellCost(locals[map], (int)locals[manY] - 1, locals[manX], -1, 1),
            //        CellCost(locals[map], locals[manY], (int)locals[manX] + 1, -1, 1),
            //        CellCost(locals[map], (int)locals[manY] + 1, locals[manX], -1, 1),
            //        CellCost(locals[map], locals[manY], (int)locals[manX] + 1, -1, 1))
            //    )
            //);

            return Code(locals, () => 
                Pair(
                    Pair(IsEmptyList(locals[nextPath]) ? 1 : 2, locals[nextPath]), 

                    !IsEmptyList(locals[path]) ? GetItem(locals[path], 0) : 
                    ((int)locals[leftcell] != Wall ? MoveLeft :
                    ((int)locals[downcell] != Wall ? MoveDown :
                    ((int)locals[rightcell] != Wall ? MoveRight : MoveUp)))));
        }

        //Поиск в глубину пилюли, сначала вверх, потом вправо, потом вниз, потом влево
/*
        public static object NextCell(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;
            //locals
            const int curcell = 5;
            const int upcell = 6;
            const int rightcell = 7;
            const int downcell = 8;
            const int leftcell = 9;

            Locals locals = null;
            locals = new Locals(args)
            {
                {curcell, () =>NormalizeCell(GetCell(locals[map], locals[y], locals[x]))},
                {upcell, () => (int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], (int)locals[y]-1, locals[x]))},
                {rightcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], locals[y], (int)locals[x]+1))},
                {downcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], (int)locals[y]+1, locals[x]))},
                {leftcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], locals[y], (int)locals[x]-1))}
            };

            return Code(locals, () =>
                    Select((int)locals[distance] > 40|| (int)locals[curcell] == Wall, -1,
                        Select((int)locals[upcell] > EmptySpace, MoveUp,
                            Select((int)locals[rightcell] > EmptySpace, MoveRight,
                                Select((int)locals[downcell] > EmptySpace, MoveDown,
                                    Select((int)locals[leftcell] > EmptySpace, MoveLeft,
                                         Select((int)locals[prevDirection] != MoveDown && (int)NextCell(locals[map], (int)locals[y] - 1, locals[x], (int)locals[distance] + 1, MoveUp) >= 0, MoveUp,
                                            Select((int)locals[prevDirection] != MoveLeft && (int)NextCell(locals[map], locals[y], (int)locals[x] + 1, (int)locals[distance] + 1, MoveRight) >= 0, MoveRight,
                                                Select((int)locals[prevDirection] != MoveUp && (int)NextCell(locals[map], (int)locals[y] + 1, (int)locals[x], (int)locals[distance] + 1, MoveDown) >= 0, MoveDown, MoveLeft))))))))
             );
        }
*/

        //Поиск в глубину пилюли, сначала вверх, потом вправо, потом вниз, потом влево
        public static object NextCell(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;
            //locals
            const int curcell = 5;
            const int upcell = 6;
            const int rightcell = 7;
            const int downcell = 8;
            const int leftcell = 9;

            Locals locals = null;
            locals = new Locals(args)
            {
                {curcell, () =>NormalizeCell(GetCell(locals[map], locals[y], locals[x]))},
                {upcell, () => (int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], (int)locals[y]-1, locals[x]))},
                {rightcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], locals[y], (int)locals[x]+1))},
                {downcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], (int)locals[y]+1, locals[x]))},
                {leftcell, () =>(int)locals[curcell] == Wall ? Wall : NormalizeCell(GetCell(locals[map], locals[y], (int)locals[x]-1))}
            };

            return Code(locals, () =>
                    Select((int)locals[distance] > 40 || (int)locals[curcell] == Wall, List(),
                        Select((int)locals[upcell] > EmptySpace, List(MoveUp),
                            Select((int)locals[rightcell] > EmptySpace, List(MoveRight),
                                Select((int)locals[downcell] > EmptySpace, List(MoveDown),
                                    Select((int)locals[leftcell] > EmptySpace, List(MoveLeft),
                                        A(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection]))))))
             );
        }

        public static object A(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;

            //locals
            Locals locals = new Locals(args);

            return Code(locals, () =>
                    (int)locals[prevDirection] != MoveDown
                        ? A2(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection],
                             (int)NextCell(locals[map], (int)locals[y] - 1, locals[x], (int)locals[distance] + 1, MoveUp))
                            : B(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection])
             );
        }

        public static object A2(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;
            const int resultList = 5;

            //locals
            Locals locals = new Locals(args);

            return Code(locals, () =>
                    IsEmptyList(locals[resultList])
                        ? B(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection])
                        : Pair(MoveUp, locals[resultList])
             );
        }

        public static object B(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;

            //locals
            Locals locals = new Locals(args);

            return Code(locals, () =>
                    (int)locals[prevDirection] != MoveLeft
                        ? B2(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection], 
                             (int)NextCell(locals[map], locals[y], (int)locals[x] + 1, (int)locals[distance] + 1, MoveRight))
                            : C(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection])
             );
        }

        public static object B2(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;
            const int resultList = 5;

            //locals
            Locals locals = new Locals(args);

            return Code(locals, () =>
                    IsEmptyList(locals[resultList])
                        ? C(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection])
                        : Pair(MoveRight, locals[resultList])
             );
        }

        public static object C(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;

            //locals
            Locals locals = new Locals(args);

            return Code(locals, () =>
                    (int)locals[prevDirection] != MoveUp
                        ? C2(locals[map], locals[y], locals[x], locals[distance], locals[prevDirection], 
                             (int)NextCell(locals[map], (int)locals[y] + 1, (int)locals[x], (int)locals[distance] + 1, MoveDown))
//                        : List(MoveLeft)
                        : List()
             );
        }

        public static object C2(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int distance = 3;
            const int prevDirection = 4;
            const int resultList = 5;

            //locals
            Locals locals = new Locals(args);

            return Code(locals, () =>
                    IsEmptyList(locals[resultList])
//                        ? List(MoveLeft)
                        ? List()
                        : Pair(MoveDown, locals[resultList])
             );
        }



        //Обработка одной вершины при поиске в глубину, возращает ценность вершины
        public static object CellCost(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int prevDirection = 3;
            const int distance = 4;
            //locals
            const int curcell = 5;

            Locals locals = null; 
            locals = new Locals(args)
            {
                {curcell, () =>GetCell(locals[map], locals[y], locals[x])}
            };

            return Code(locals, () => 
                    (int)Select((int)locals[distance] > 1 || (int)locals[curcell] == Wall, 0, 
                        (int)Select((int) GetCell(locals[map], (int) locals[y] - 1, locals[x]) == Wall || (int) locals[prevDirection] == MoveDown,
                            0,
                            CellCost(locals[map], (int) locals[y] - 1, locals[x], MoveUp, (int) locals[distance] + 1)) +
                        (int)Select((int)GetCell(locals[map], locals[y], (int)locals[x] + 1) == Wall || (int) locals[prevDirection] == MoveLeft,
                            0,
                            CellCost(locals[map], locals[y], (int)locals[x] + 1, MoveRight, (int)locals[distance] + 1)) + 
                        (int)Select((int) GetCell(locals[map], (int)locals[y] + 1, locals[x]) == Wall || (int) locals[prevDirection] == MoveUp,
                            0,
                            CellCost(locals[map], (int)locals[y] + 1, locals[x], MoveDown, (int) locals[distance] + 1)) +
                        (int)Select((int)GetCell(locals[map], locals[y], (int)locals[x] - 1) == Wall || (int) locals[prevDirection] == MoveRight,
                            0,
                            CellCost(locals[map], locals[y], (int)locals[x] - 1, MoveLeft, (int)locals[distance] + 1))) +
                     (int)Select((int)locals[curcell] == Fruit, FruitPriсe * (20000 / (int)locals[distance] / (int)locals[distance]),
                     (int)Select((int)locals[curcell] == PowerPill, PowerPillPriсe * (20000 / (int)locals[distance] / (int)locals[distance]),
                     (int)Select((int)locals[curcell] == Pill, PillPriсe * (20000 / (int)locals[distance] / (int)locals[distance]), 0))));
        }

        public static object GetMaxNeighbor(params object[] args)
        {
            const int map = 0;
            const int manY = 1;
            const int manX = 2;
            var locals = new Locals(args);

            return Code(locals, () =>
                Max4(
                    NormalizeCell(GetCell(locals[map], (int)locals[manY] - 1, locals[manX])),
                    NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX] + 1)),
                    NormalizeCell(GetCell(locals[map], (int)locals[manY] + 1, locals[manX])),
                    NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX] - 1)))
            );
        }

        public static object NormalizeCell(params object[] args)
        {
            const int cell = 0;
            var locals = new Locals(args);
            return Code(() =>
                (int)locals[cell] >= ManStartPosition ? EmptySpace : locals[cell]
                );
        }

        public static object Max3(params object[] args)
        {
            var locals = new Locals(args);
            return Code(() =>
                Max2(locals[0], Max2(locals[1], locals[2]))
            );
        }

        public static object Max4(params object[] args)
        {
            var locals = new Locals(args);
            return Code(() =>
                Max2(locals[0], Max2(locals[1], Max2(locals[2], locals[3])))
            );
        }

        public static object Max4Index(params object[] args)
        {
            const int max = 4;
            Locals locals = null;
            locals = new Locals(args)
                {
                   {max, () => Max4(locals[0], locals[1], locals[2], locals[3])} 
                };
            return Code(locals, () =>
                Select((int)locals[max] == (int)locals[0], 0,
                Select((int)locals[max] == (int)locals[1], 1,
                Select((int)locals[max] == (int)locals[2], 2, 3)))
            );
        }

        public static object GetCell(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            var locals = new Locals(args);
            return Code(() =>
                GetItem(GetItem(locals[map], (int)locals[y]), (int)locals[x])
            );
        }
    }
}